use serde::{Deserialize, Serialize};

use crate::{BlockChainBaseError, constants::COMPACT_SIGNATURE_LENGTH};

#[derive(Debug, Clone, PartialEq)]
pub struct CompactSignatureBytes([u8; COMPACT_SIGNATURE_LENGTH]);

impl std::fmt::Display for CompactSignatureBytes {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.to_hex_str())
    }
}

impl CompactSignatureBytes {
    pub fn new(bytes: [u8; COMPACT_SIGNATURE_LENGTH]) -> Self {
        Self(bytes)
    }

    pub fn as_bytes_ref(&self) -> &[u8; COMPACT_SIGNATURE_LENGTH] {
        &self.0
    }

    pub fn from_vec(vec: Vec<u8>) -> Result<Self, BlockChainBaseError> {
        if vec.len() < COMPACT_SIGNATURE_LENGTH {
            return Err(BlockChainBaseError::CompactSigBytesVecLenTooShort);
        }

        let mut array = [0u8; COMPACT_SIGNATURE_LENGTH];
        array.copy_from_slice(&vec[..COMPACT_SIGNATURE_LENGTH]);

        Ok(CompactSignatureBytes(array))
    }

    pub fn to_hex_str(&self) -> String {
        hex::encode(self.0)
    }
}

impl Serialize for CompactSignatureBytes {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        use serde::ser::SerializeTuple;
        let mut seq = serializer.serialize_tuple(COMPACT_SIGNATURE_LENGTH)?;
        for byte in self.0.iter() {
            seq.serialize_element(byte)?;
        }
        seq.end()
    }
}

impl<'de> Deserialize<'de> for CompactSignatureBytes {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        struct CompactSignatureVisitor;

        impl<'de> serde::de::Visitor<'de> for CompactSignatureVisitor {
            type Value = CompactSignatureBytes;

            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
                formatter
                    .write_str(format!("an array of {} bytes", COMPACT_SIGNATURE_LENGTH).as_str())
            }

            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
            where
                A: serde::de::SeqAccess<'de>,
            {
                let mut bytes = [0u8; COMPACT_SIGNATURE_LENGTH];
                for (i, it) in bytes.iter_mut().enumerate().take(COMPACT_SIGNATURE_LENGTH) {
                    *it = seq.next_element()?.ok_or_else(|| {
                        serde::de::Error::invalid_length(
                            i,
                            &format!("array of length {}", COMPACT_SIGNATURE_LENGTH).as_str(),
                        )
                    })?;
                }
                Ok(CompactSignatureBytes(bytes))
            }
        }

        deserializer.deserialize_tuple(COMPACT_SIGNATURE_LENGTH, CompactSignatureVisitor)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    // use serde_json;

    #[test]
    fn test_new() {
        let bytes = [0u8; COMPACT_SIGNATURE_LENGTH];
        let sig = CompactSignatureBytes::new(bytes);
        assert_eq!(sig.0, bytes);
    }

    #[test]
    fn test_as_bytes_ref() {
        let bytes = [1u8; COMPACT_SIGNATURE_LENGTH];
        let sig = CompactSignatureBytes::new(bytes);
        assert_eq!(sig.as_bytes_ref(), &bytes);
    }

    #[test]
    fn test_from_vec() {
        let vec = vec![2u8; COMPACT_SIGNATURE_LENGTH];
        let sig = CompactSignatureBytes::from_vec(vec).unwrap();
        let expected = [2u8; COMPACT_SIGNATURE_LENGTH];
        assert_eq!(sig.0, expected);
    }

    #[test]
    fn test_from_vec_too_short() {
        let vec = vec![3u8; COMPACT_SIGNATURE_LENGTH - 1];
        let result = CompactSignatureBytes::from_vec(vec);
        assert_eq!(
            result,
            Err(BlockChainBaseError::CompactSigBytesVecLenTooShort)
        );
    }

    #[test]
    fn test_serde() {
        let bytes = [4u8; COMPACT_SIGNATURE_LENGTH];

        let sig = CompactSignatureBytes::new(bytes);

        let serialized = bincode::serialize(&sig).unwrap();
        assert_eq!(serialized, bytes);

        let deserialized: CompactSignatureBytes = bincode::deserialize(&serialized).unwrap();
        assert_eq!(sig, deserialized);
    }
}
